package com.terry.test2.old.impl;

import com.terry.test2.Result;
import com.terry.test2.old.*;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * @author 小八
 * @date 2023年02月01日11:15
 *
 * 可维护性差:
 * 1.AccountDO 是一个纯数据结构，映射了数据库的一个表， 数据库的表结构和设计师应用的外部依赖，
 * 长远看可与会改变
 * 2.依赖库可能会升级,AccountMapper依赖mybatis的实现，如果mybatis更换,影响很大，同样换一个orm体系
 * 迁移成本也是巨大的
 * 3.第三方服务依赖的不确定性  服务可以变化或者不可能，同时也要做外部依赖的兜底，限流，熔断
 * 4.第三方服务的api接口变化
 * 5.中间件更换
 *
 * 可扩展性：
 * 1.数据来源被固定,数据格式不兼容
 * 原有的AccountDO是从本地获取的，而跨行转账的数据可能需要从一个第三方服务获取
 * 2.业务逻辑无法复用
 * 数据格式不兼容问题会导致核心业务逻辑无法复用。每个用例都是特殊逻辑，后果是最终会
 * 造成大量的if-else语句
 * 3.逻辑和数据存储的相互依赖
 *
 * 在事务脚本式的架构下,往往做第一个需求很快,但是做N个需求所需要的时间呈指数上升
 * 可测试性差
 *
 * 违背了几个设计原则：
 * 1.单一性原则：
 *  单一性原则要求一个对象/类应该只有一个变更的原因。但是在这个案例里，代码可能会因为任意一个外部依赖或计算逻辑的改变而改变。
 * 2.依赖反转原则
 *  程序应该依赖抽象，而不是依赖具体的实现。YahooForexService 和 KafkaTemplate,Mybatis的DAO实现都属于具体实现。
 * 3.开放封闭原则
 * 对扩展开放,对封闭修改
 * 在这个案例里金额计算属于会被修改的代码,但是计算逻辑应该需要被包装成为不可修改的计算类.新功能
 * 通过计算类的扩展实现。
 *
 */
public class TransferServiceImpl implements TransferService {

    private AccountMapper accountDao;
    private YahoolForexService yahoolForex;
    private KafkaTemplate kafkaTemplate;
    private String TOPIC_KAFKA = "TOPIC_KAFKA";

    @Override
    public Result<Boolean> transfer(Long sourceUserId, String targetAccountNumber, BigDecimal targetAmount, String targetCurrency) {
        //1.从数据中读取账户数据
        AccountDO sourceAccountDO = accountDao.selectByUserId(sourceUserId);
        AccountDO targetAccountDO = accountDao.selectByAccountNumber(targetAccountNumber);

        //2.业务参数校验 不是目标汇率
        if(!targetAccountDO.getCurrency().equals(targetCurrency)){
            throw new InvalidCurrencyException();
        }

        //3.获取外部数据，并且包含一定的业务逻辑
        BigDecimal exchangeRate = BigDecimal.ZERO;
        if(!sourceAccountDO.getCurrency().equals(targetCurrency)){
            exchangeRate = yahoolForex.getExchange(sourceAccountDO.getCurrency(),targetCurrency);
        }
        BigDecimal sourceAmount = targetAmount.divide(exchangeRate, RoundingMode.DOWN);

        //4.业务参数校验
        if(sourceAccountDO.getAvailable().compareTo(sourceAmount)<0){
            throw new InsufficientFundsException();
        }
        if(sourceAccountDO.getDailyLimit().compareTo(sourceAmount)<0){
            throw new InsufficientFundsException();
        }

        //5.计算新值,并且更新字段
        BigDecimal newSource = sourceAccountDO.getAvailable().subtract(sourceAmount);
        BigDecimal newTarget = targetAccountDO.getAvailable().add(targetAmount);
        sourceAccountDO.setAvailable(newSource);
        targetAccountDO.setAvailable(newTarget);

        //6.更新到数据库
        accountDao.update(sourceAccountDO);
        accountDao.update(targetAccountDO);

        //7.发送mq消息
        String message = sourceUserId + ","+ targetAccountNumber + "," + targetAmount+","+targetCurrency;
        kafkaTemplate.send(TOPIC_KAFKA,message);
        return Result.success(true);
    }

}
